fix(drill): the list URL carries a boolean, a set, $ne and the text operators, so Open in list and View all regain an exact destination - #30777
Conversation
…perators, so "Open in list" and "View all" open the exact list again The object list route gains `filter[FIELD][bool]=true|false`, the `[ne|contains|notcontains|startswith|endswith|icontains]` suffixes and the repeated `[in]` / `[nin]` membership params. Each reads back through parseUrlFilterTriples as the node convertFiltersToAST lowers the same condition to, comparand type included, and exactRecordListFilter now proves that per condition before it accepts one. A drill on a yes/no bucket or under an $in / $ne / text-operator scope, and a related list's "View all" with such a filter, get their list destination back; $or, $between, an empty set and a boolean under $ne stay refused. Claude-Session: https://claude.ai/code/session_01L6bhgW9yrPaVhdtD84RWcR Co-authored-by: Claude <noreply@anthropic.com>
|
domain:ui#1 · os-sales · session_01L6bhgWn6zhNa4LL7VB1rtL Contract reviewServed-tier: Face: declaration only ( ① Derived judgments
② Semver level
③ Boundary flagsThe os-dev-report 6675412242 has
Implemented-by: VERDICT: PASS |
|
domain:ui#1 · os-sales · session_01L6bhgWn6zhNa4LL7VB1rtL 维护者速读
席位核对 — ACCEPT · head ed32291
|
Requested by Sales · project thread
Fixes #30743
Clause-②: yes (widening)
Metadata-protocol: none
维护者速读
/apps/APP/OBJECT/data的 URL 新增六种写法:布尔filter[F][bool]=true|false、$ne与五个文本运算符filter[F][ne|contains|notcontains|startswith|endswith|icontains]=V、集合filter[F][in]=A&filter[F][in]=B(及[nin])。下钻「在列表中打开」、drillDown.target: 'navigate'和相关列表「查看全部」遇到这些条件时恢复去处。$in#30654 之后,凡是 URL 说不全的下钻都不给出口;按是/否字段分组的图表、status in (...)这类度量范围最常见,全部没了去处。实测(见下):布尔原样写出会读回成字符串'true',其余写法根本写不出参数。$or、$between、空集合、布尔的$ne仍然不给出口。旧链接filter[F]=true含义不变(文本)。回滚即 revert。ui/packages/app-shell/src/views/drillUrlFilters.ts(新常量与读写两侧)、ui/packages/app-shell/src/views/useOpenRecordList.ts(exactRecordListFilter的接受集与isSameComparison)、ui/packages/app-shell/src/views/ObjectDataPage.tsx(筛选标签渲染与「另存为视图」桥接)。/apps/APP/OBJECT/data?filter[status][in]=paid&filter[status][in]=sent,预期列表只含这两种状态,顶部标签显示「In paid, sent」(中文包为对应译文),点标签上的 ✕ 两个参数一起移除。What changes
Measured first, on
origin/mainb3098661, through the realtoFilterNode→parseFilterAST→serializeDrillFilterParams→parseUrlFilterTripleschain, againstconvertFiltersToAST(what the drill drawer queries):{ is_active: true }[is_active, '=', true]filter[is_active]=true[is_active, '=', 'true'](a string)$in/$nin[status, 'in', [...]]$ne[status, '!=', 'void']$contains,$notContains,$startsWith,$endsWith,$icontains[name, 'contains', 'acme']…All 18 probed cases answered
exactRecordListFilter→null. After this PR every one of them round-trips to the drawer's own node, except the three that must not (an empty$in, a boolean$ne), which still answernull.ui/packages/app-shell/src/views/drillUrlFilters.tsBOOLEAN_FILTER([bool]): the only two spellings aretrue/false, read back as the boolean; anything else in that slot is dropped, never read as text. A bare boolean in a drill filter is written there instead offilter[F]=true.URL_COMPARE_OPS/COMPARE_OP_PARAM:ne → '!='and the five text operators, suffix → the AST wordconvertFiltersToASTemits. Prototype-free, likeURL_FILTER_OPS, which keeps its four pinned entries.URL_LIST_OPS/LIST_OP_PARAM:in/ninas a repeated param, one member per occurrence (no delimiter, so a member may contain a comma). The read side gathers every occurrence on one field into one triple at the first occurrence's position. The write side writes the whole list or nothing (an empty list, an empty member or a boolean member writes no param), and a later source replaces an earlier list on the same field, thesetposture of every other param.URL_TRIPLE_OP_WORD: AST word → canonicalViewFilterRuleword, read by the chips (label keyfilterBuilder.operators.WORD, existing in all ten packs) and by "Save as view".groupFilterChips: a negation, text or membership chip carries the operator label astextKeyand the comparand astext(a set joined with a comma).FilterChip.textKey's doc now says it may sit besidetext. Every existing chip is unchanged.ui/packages/app-shell/src/views/useOpenRecordList.tsexactRecordListFilteraccepts a boolean under equality (bare or$eq),$newith a string or finite number, the text operators with a non-empty string, and$in/$ninwith a non-empty list of strings or finite numbers.convertFiltersToAST(condition), field, operator and comparand, with the one difference the route has always carried (a finite number reads back as its text). A boolean reading back as'true'fails this check.ui/packages/app-shell/src/views/ObjectDataPage.tsxt(textKey) + ' ' + textwhen it carries both.TRIPLE_OP_TO_SPEC_ALIASspreadsURL_TRIPLE_OP_WORD, so "Save as view" folds!=tonot_equals,nintonot_in, and so on; the record gate passes.One changeset:
@object-ui/app-shellpatch.Tests
drillUrlFilters.test.ts: a new describe for the six spellings: write/read for each againstconvertFiltersToAST, only two[bool]spellings,filter[name]=truestill text, set order and grouping, whole-list-or-nothing, later set replaces, boolean$newrites nothing, prototype sweep over the new lookups, chips, prefix delete of a set, equality-only route still executes none.exactRecordListFilter-25888.test.ts: the new accepted spellings round-trip; each equals the drawer node; numbers in a set; a set beside an equality; a repeated set written once; new refusals (empty set, empty member, boolean member, two different sets, boolean$ne, text operator with a number,$between).useOpenRecordList.placeholderScope-9022.test.tsx: through the real host, an$inscope and a boolean bucket open the exact URL; a boolean$nestill opens nothing.ObjectDataPage.saveAsViewFilterFold.test.ts: every new operator folds to a canonical rule and theViewItemSchemagate passes; the operator sets are computed from the exported maps.ObjectDataPage.filterChipI18n-9159.test.tsx: on the real page with the real packs,[in]/[nin]/[ne]chips draw the zh label beside the comparand, and the en control differs.Moved pins (intended, each the behaviour this card asks to change):
exactRecordListFilter-25888: four refusals (set operator, negation, text operator, boolean comparand) became accepted cases.useOpenRecordList.placeholderScope-9022: "an$inscope offers no destination" now opens the exact list.ObjectView.urlFilterSuffix-9196: row 1 used[contains]as its unknown suffix; it now uses[like], and a new row pins[contains]executing on/dataonly while the object route still drops it. Its non-vacuity battery gains the/datacontains triple.Acceptance notes
= true/= false: the raw comparand, the posture every equality chip already has (an option label is not drawn either).['1', '2']), the posture equality and the range bounds have always had; the exactness check allows exactly that difference and nothing else.$orand every other combinator,$between, an empty set, a set member that is empty or boolean,$neon a boolean, a text operator with a non-string, two different comparands or sets in one param.ObjectView, equality-only, objectui#9196's boundary) learns none of the new suffixes.ui/packages/i18n/src/locales/*.tsuntouched: the chip labels are the filter builder's existingfilterBuilder.operators.*keys.ObjectDataPage.filterChipI18n-9159.test.tsx; this PR appends one describe at the end of that file. Whichever lands second merges the other.ObjectDataPagerender and the realuseRecordListDrillNavigationhost in the tests above.Verification
All readings on head
ed322918.Targeted suites: 21 files, 383 tests, all green
pnpm exec vitest run --maxWorkers=2fromui/overdrillUrlFilters.test.ts,exactRecordListFilter-25888, bothRelatedRecordActionsBridgeView-all suites (12741, 25888),useOpenRecordList.placeholderScope-9022,ObjectView.urlFilterSuffix-9196,ObjectView.filterParamsKeyEncoding-9287, everyObjectDataPage.*suite, everydrill*suite in app-shell views,studio-canvas-preview, and plugin-dashboard'sDatasetWidgetandObjectPivotTable.drillFilterComposition-9024:Test Files 21 passed (21),Tests 383 passed (383). Before the pin moves the same run read7 failed | 339 passed, the seven moved pins listed above and nothing else.Type-check: clean
pnpm --filter @object-ui/app-shell type-check(bothtsc --noEmitandtsc -p tsconfig.test.json, after building the@object-ui/app-shell^...closure): 0error TS,VERDICT command-exit 0.Ablation: one mutation, three reds, restored
node scripts/ablation-replace.mjsondrillUrlFilters.ts, anchorout.push([field, '=', true])in the[bool]read arm replaced with the string'true'(anchor 1 → 0, blob35873c7f→0120ef72):drillUrlFilters.test.ts"reads the[bool]slot back as the BOOLEAN the drawer compares with" red, andexactRecordListFilter-25888"writes the spellings objectstack#30743 added" plus "reads each of those back as the node the drill drawer queries" red (the gate's comparand-type check refusing the string).Tests 3 failed | 116 passed. Restored: blob == HEAD,git diff HEADempty.Derived gates
node scripts/pm/dispatch-gates.mjs --commandsnames 35 families for this change set (10 paths, 642 changed lines, under the 3000 threshold). Running locally at open time; the readings and the--ranreconciliation are in the os-dev-report comment on #30743. Every family CI runs is read from this PR's check-runs.Human Minimum
packages/specholds no runtime logic. (packages/specuntouched.)pnpm lintis CI's run.main, one pull request.packages/spectouched: not touched.content/docs/references/,packages/*/CHANGELOG.mdorcontent/docs/releases/.Generated by Claude Code
🤖 Generated with Claude Code
https://claude.ai/code/session_01L6bhgW9yrPaVhdtD84RWcR